/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   * 通过原型继承的方式，创建一个Vue的子类Sub并返回
   * extendOptions: 通常是Vue选项对象，
   */
  Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
    // todo(need confirm): 同一个组件，使用的同一份options，如果data是一个对象，不同的组件实例会相互影响
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // Super就是上面定义的this，即Vue
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    // Sub子类的options：将Super.options与传入的extendOptions合并，
    // 之后初始化Sub子类的时候，Sub.options就是"自身options"与"vm.constructor.options(Sub.options)"合并后的结果，参考core\instance\init.js 44行。
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 对于props和computed属性，我们在extend阶段定义代理getter到Vue实例上，这样可以避免在每个组件实例创建时,再去调用Object.defineProperty
    if (Sub.options.props) {
      // 子类原型上挂'_props'属性：proxy(Comp.prototype, `_props`, key)
      // var vm = new Sub()，当我们取props值时：
      // vm._propsA -> vm._props._propsA,, 在initProps阶段，所有的’props‘已经挂载到vm._props上了
      // 目的主要是利用原型链机制共享数据，减少重复创建，且多组件实例间共享
      initProps(Sub)
    }
    if (Sub.options.computed) {
      // 子类原型上挂载key，然后调用'defineComputed(Comp.prototype, key, computed[key])'
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    // ASSET_TYPES = [ 'component', 'directive', 'filter' ]
    // 将子类的component、directive、filter方法，指向父类的对应方法
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // 用于递归组件调用，可通过Sub.options.components['name']找到自身,例如：Sub.options.components['App'] = Sub
    // enable recursive(/rɪˈkɜːrsɪv/ ) self-lookup
    if (name) { // 递归组件必须有name属性
      // 将子类构造函数Sub，放到Sub.options.components上
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    // 缓存构造函数，同一个options选项，最终只会有一个Sub子类
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}
